<!DOCTYPE html>
<html>
  <style>
    #controll {
      border: 1px solid black;
      padding: 1em;
      margin: 1em;
    }
  </style>
  <body>
    <div id="controll">
      <input id="srcText" />
    </div>
    <div id="target"></div>
    <div>
      <tr>
        <th>this is a th</th>
        <th>this is a th</th>
        <th>this is a th</th>
      </tr>
    </div>
  </body>
  <script type="text/javascript">
    class PropSubject {
      constructor(src, propName) {
        this.src = src;
        this.propName = propName;
        this.obs = [];
      }

      setValue(value) {
        var oldValue = this.src[this.propName];
        if (oldValue === value) {
          return;
        }
        console.log('set data ');
        this.src[this.propName] = value;
        this._notify();
      }

      getValue() {
        console.log('get data ');
        return this.src[this.propName];
      }

      sub(obs) {
        this.obs.push(obs);
      }

      _notify() {
        this.obs.forEach((ob) => {
          ob();
        });
      }
    }

    /**
     * 创建对象代理
     */
    createObjectProxy = (target) => {
      var proxy = {};
      var props = Object.getOwnPropertyNames(target);
      props.forEach((targetObjectPropName) => {
        var subject = new PropSubject(target, targetObjectPropName);
        Object.defineProperty(proxy, targetObjectPropName, {
          value: subject,
          writable: false,
        });
      });
      console.log('create object proxy ... ');
      return proxy;
    };

    var props = { text1: 'this is a value ', arr1: [1, 2, 3, 4, 5, 7, 8, 9] };

    var objectProxy = createObjectProxy(props);

    var vdom = {
      type: 'div',
      children: [
        {
          type: 'div',
          children: [
            {
              type: 'input',
              value: props.text1,
            },
            {
              type: 'text',
              value: props.text1,
            },
          ],
        },
        {
          type: 'input',
          value: objectProxy.text1,
        },
        {
          type: 'text',
          value: objectProxy.text1,
        },
        {
          type: 'loop',
          value: objectProxy.arr1,
          children: [
            {
              type: 'div',
              children: [
                {
                  type: 'input',
                  value: props.text1,
                },
                {
                  type: 'text',
                  value: objectProxy.text1,
                },
              ],
            },
          ],
        },
      ],
    };

    function clearChildren(vdom) {
      var element = vdom.element;
      if (element) {
        while (element.firstChild) {
          element.removeChild(element.firstChild);
        }
      }
    }

    function childrenElementFactory(vdom, element) {
      if (vdom.children) {
        vdom.children.forEach((child) => {
          var childElement = elementFactory(child);
          element.append(childElement);
        });
      }
    }

    function elementFactory(vdom) {
      var element = null;
      var isContainer = false;
      switch (vdom.type) {
        case 'div':
          element = document.createElement('div');
          childrenElementFactory(vdom, element);
          break;
        case 'text':
          element = document.createElement('span');

          if (vdom.value instanceof PropSubject) {
            element.innerText = vdom.value.getValue() || '';
            vdom.value.sub(() => {
              element.innerText = vdom.value.getValue() || '';
            }, vdom);
          } else {
            element.innerText = vdom.value || '';
          }
          break;
        case 'input':
          element = document.createElement('input');

          if (vdom.value instanceof PropSubject) {
            element.value = vdom.value.getValue() || '';
            vdom.value.sub(() => {
              element.value = vdom.value.getValue() || '';
            }, vdom);
          } else {
            element.value = vdom.value || '';
          }
          break;
        case 'loop':
          element = document.createElement('div');
          if (vdom.value instanceof PropSubject) {
            let loopValue = null;
            loopValue = vdom.value.getValue() || '';
            loopValue.forEach((item) => {
              childrenElementFactory(vdom, element);
            });
            vdom.value.sub(() => {
              clearChildren(vdom);
              loopValue = vdom.value.getValue() || '';
              loopValue.forEach((item) => {
                childrenElementFactory(vdom, element);
              });
            }, vdom);
          } else {
            vdom.value.forEach((item) => {
              childrenElementFactory(vdom, element);
            });
          }

          break;
      }

      element.setAttribute('data-render-time', new Date().getTime());
      element.setAttribute('data-type', vdom.type);

      if (isContainer && vdom.children) {
        vdom.children.forEach((child) => {
          var childElement = elementFactory(child);
          element.append(childElement);
        });
      }

      if (!element) {
        throw new Error('element type is not supported ');
      }
      return element;
    }

    var srcText = document.getElementById('srcText');

    var target = document.getElementById('target');
    srcText.oninput = (event) => {
      var text = srcText.value;
      objectProxy.text1.setValue(text);
      var arr = [];

      for (var i = 0; i < text.length; i++) {
        arr.push(text.charAt(i));
      }
      objectProxy.arr1.setValue(arr);
    };

    function render(vdom, target) {
      console.log('render start ... ');

      vdom.element = elementFactory(vdom);
      target.append(vdom.element);

      console.log('render finished ... ');
    }
    render(vdom, target);

    /*
    table {
      --- culums : [
      ---  {
              filedName : '',
              lableName : '',
              lableSlot:null,
              fieldSolt:null
      ---  }
      ---]
      prop: {
        data : Itarator
      },
      isContainer:true,
      childLimited : ['Base.TableRow'],
      real: {
        row : {
          loop : {
            src : columns,
            indexVariableName : 'index', 
            valueVariableName : 'column',
            template : {ref: '=column.head'}
          }
        }

        loop : {
          src: data,
          indexVariableName : 'index', 
          valueVariableName : 'item', 
          template :{
            row : {
              loop : {
                src : columns,
                indexVariableName : 'index', 
                valueVariableName : 'column',
                template : {ref: '=column.field'}
              }
            }        
          }      
        }
      }
    }
    */
  </script>
</html>
